/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ECMASCRIPT_JS_DATE_TIME_FORMAT_H
#define ECMASCRIPT_JS_DATE_TIME_FORMAT_H

#include "js_locale.h"

namespace ark::ecmascript {
enum class DateTimeStyleOption : uint8_t { FULL = 0x01, LONG, MEDIUM, SHORT, UNDEFINED, EXCEPTION };
enum class DefaultsOption : uint8_t { DATE = 0x01, TIME, ALL };
enum class HourCycleOption : uint8_t { H11 = 0x01, H12, H23, H24, UNDEFINED, EXCEPTION };
enum class RequiredOption : uint8_t { DATE = 0x01, TIME, ANY };
enum class Value : uint8_t { SHARED, START_RANGE, END_RANGE };

constexpr int CAPACITY_3 = 3;
constexpr int CAPACITY_4 = 4;
constexpr int CAPACITY_5 = 5;
constexpr int CAPACITY_8 = 8;
constexpr int STRING_LENGTH_2 = 2;
constexpr int STRING_LENGTH_3 = 3;
constexpr int STRING_LENGTH_7 = 7;
constexpr int STRING_LENGTH_8 = 8;
constexpr int STRING_LENGTH_9 = 9;
constexpr int STRING_LENGTH_10 = 10;

class IcuPatternDesc;

std::vector<IcuPatternDesc> BuildIcuPatternDescs();
std::vector<IcuPatternDesc> InitializePattern(const IcuPatternDesc &hourData);

using IcuPatternDescVect = std::vector<IcuPatternDesc>;
using IcuPatternEntry = std::pair<std::string, std::string>;

class IcuPatternDesc {
public:
    IcuPatternDesc(std::string propertyParam, std::vector<IcuPatternEntry> pairsParam,
                   std::vector<std::string> allowedValuesParam)
        : property(std::move(propertyParam)), pairs(std::move(pairsParam)), allowedValues(std::move(allowedValuesParam))
    {
        for (const auto &pair : pairs) {
            map.insert(std::make_pair(pair.second, pair.first));
        }
    }

    virtual ~IcuPatternDesc() = default;

    std::string property;                                // NOLINT(misc-non-private-member-variables-in-classes)
    std::vector<IcuPatternEntry> pairs;                  // NOLINT(misc-non-private-member-variables-in-classes)
    std::map<const std::string, const std::string> map;  // NOLINT(misc-non-private-member-variables-in-classes)
    std::vector<std::string> allowedValues;              // NOLINT(misc-non-private-member-variables-in-classes)

    DEFAULT_COPY_SEMANTIC(IcuPatternDesc);
    // NOLINT(performance-noexcept-move-constructor, hicpp-noexcept-move)
    DEFAULT_NOEXCEPT_MOVE_SEMANTIC(IcuPatternDesc);
};

class Pattern {
public:
    Pattern(const std::string &data1, const std::string &data2)
        : data_(InitializePattern(
              IcuPatternDesc("hour", {{data1, "2-digit"}, {data2, "numeric"}}, {"2-digit", "numeric"})))
    {
    }
    virtual ~Pattern() = default;
    std::vector<IcuPatternDesc> Get() const
    {
        return data_;
    }

private:
    std::vector<IcuPatternDesc> data_ {};
    NO_COPY_SEMANTIC(Pattern);
    NO_MOVE_SEMANTIC(Pattern);
};

class JSDateTimeFormat : public JSObject {
public:
    CAST_CHECK(JSDateTimeFormat, IsJSDateTimeFormat);

    ACCESSORS_BASE(JSObject)
    ACCESSORS(0, Locale)
    ACCESSORS(1, Calendar)
    ACCESSORS(2, NumberingSystem)
    ACCESSORS(3, TimeZone)
    ACCESSORS(4, HourCycle)
    ACCESSORS(5, LocaleIcu)
    ACCESSORS(6, SimpleDateTimeFormatIcu)
    ACCESSORS(7, Iso8601)
    ACCESSORS(8, DateStyle)
    ACCESSORS(9, TimeStyle)
    ACCESSORS(10, BoundFormat)
    ACCESSORS_FINISH(11)

    DECL_DUMP()

    icu::Locale *GetIcuLocale() const;
    static void SetIcuLocale(JSThread *thread, JSHandle<JSDateTimeFormat> obj, const icu::Locale &icuLocale,
                             const DeleteEntryPoint &callback);
    static void FreeIcuLocale(void *pointer, void *data);

    icu::SimpleDateFormat *GetIcuSimpleDateFormat() const;
    static void SetIcuSimpleDateFormat(JSThread *thread, JSHandle<JSDateTimeFormat> obj,
                                       const icu::SimpleDateFormat &icuSimpleDateTimeFormat,
                                       const DeleteEntryPoint &callback);
    static void FreeSimpleDateFormat(void *pointer, void *data);

    // 13.1.1 InitializeDateTimeFormat (dateTimeFormat, locales, options)
    static JSHandle<JSDateTimeFormat> InitializeDateTimeFormat(JSThread *thread,
                                                               const JSHandle<JSDateTimeFormat> &dateTimeFormat,
                                                               const JSHandle<JSTaggedValue> &locales,
                                                               const JSHandle<JSTaggedValue> &options);

    // 13.1.2 ToDateTimeOptions (options, required, defaults)
    static JSHandle<JSObject> ToDateTimeOptions(JSThread *thread, const JSHandle<JSTaggedValue> &options,
                                                const RequiredOption &required, const DefaultsOption &defaults);

    // 13.1.7 FormatDateTime(dateTimeFormat, x)
    static JSHandle<EcmaString> FormatDateTime(JSThread *thread, const JSHandle<JSDateTimeFormat> &dateTimeFormat,
                                               double x);

    // 13.1.8 FormatDateTimeToParts (dateTimeFormat, x)
    static JSHandle<JSArray> FormatDateTimeToParts(JSThread *thread, const JSHandle<JSDateTimeFormat> &dateTimeFormat,
                                                   double x);

    // 13.1.10 UnwrapDateTimeFormat(dtf)
    static JSHandle<JSTaggedValue> UnwrapDateTimeFormat(JSThread *thread,
                                                        const JSHandle<JSTaggedValue> &dateTimeFormat);

    static JSHandle<TaggedArray> GainAvailableLocales(JSThread *thread);

    static void ResolvedOptions(JSThread *thread, const JSHandle<JSDateTimeFormat> &dateTimeFormat,
                                const JSHandle<JSObject> &options);

    static JSHandle<EcmaString> NormDateTimeRange(JSThread *thread, const JSHandle<JSDateTimeFormat> &dtf, double x,
                                                  double y);

    static JSHandle<JSArray> NormDateTimeRangeToParts(JSThread *thread, const JSHandle<JSDateTimeFormat> &dtf, double x,
                                                      double y);

private:
    static HourCycleOption OptionToHourCycle(const std::string &hc);

    static Value TrackValue(int32_t beginning, int32_t ending, std::array<int32_t, 2> begin,
                            std::array<int32_t, 2> end);

    static HourCycleOption OptionToHourCycle(UDateFormatHourCycle hc);

    static std::string ToHourCycleString(int32_t hc);

    static std::unique_ptr<icu::TimeZone> ConstructTimeZone(const std::string &timezone);

    static std::string ConstructFormattedTimeZoneID(const std::string &input);

    static std::string ToTitleCaseTimezonePosition(const std::string &input);

    static std::unique_ptr<icu::DateIntervalFormat> ConstructDateIntervalFormat(const JSHandle<JSDateTimeFormat> &dtf);

    static std::string ConstructGMTTimeZoneID(const std::string &input);

    static std::unique_ptr<icu::Calendar> BuildCalendar(const icu::Locale &locale, const icu::TimeZone &timeZone);

    static std::map<std::string, std::string> GetSpecialTimeZoneMap();

    static JSHandle<JSArray> ConstructFDateIntervalToJSArray(JSThread *thread,
                                                             const icu::FormattedDateInterval &formatted);

    static std::vector<IcuPatternDesc> GetIcuPatternDesc(const HourCycleOption &hourCycle);

    static std::unique_ptr<icu::SimpleDateFormat> CreateICUSimpleDateFormat(const icu::Locale &icuLocale,
                                                                            const icu::UnicodeString &skeleton,
                                                                            icu::DateTimePatternGenerator *gn,
                                                                            HourCycleOption hc);

    static JSHandle<JSTaggedValue> ConvertFieldIdToDateType(JSThread *thread, int32_t fieldId);

    static icu::UnicodeString ChangeHourCyclePattern(const icu::UnicodeString &pattern, HourCycleOption hc);

    static std::string ToTitleCaseFunction(const std::string &input);

    static bool IsValidTimeZoneInput(const std::string &input);

    static JSHandle<EcmaString> ToValueString(JSThread *thread, Value value);

    static icu::FormattedDateInterval ConstructDTFRange(JSThread *thread, const JSHandle<JSDateTimeFormat> &dtf,
                                                        double x, double y);
};
}  // namespace ark::ecmascript
#endif  // ECMASCRIPT_JS_DATE_TIME_FORMAT_H
